home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 1843 / 1843.xpi / content / firebug / html.js < prev    next >
Text File  |  2010-01-15  |  67KB  |  2,101 lines

  1. /* See license.txt for terms of usage */
  2.  
  3. FBL.ns(function() { with (FBL) {
  4.  
  5. // ************************************************************************************************
  6. // Constants
  7.  
  8. const Cc = Components.classes;
  9. const Ci = Components.interfaces;
  10.  
  11. const MODIFICATION = MutationEvent.MODIFICATION;
  12. const ADDITION = MutationEvent.ADDITION;
  13. const REMOVAL = MutationEvent.REMOVAL;
  14.  
  15. const HTMLLib = Firebug.HTMLLib;
  16.  
  17. const BP_BREAKONATTRCHANGE = 1;
  18. const BP_BREAKONCHILDCHANGE = 2;
  19. const BP_BREAKONREMOVE = 3;
  20. const BP_BREAKONTEXT = 4;
  21.  
  22. // ************************************************************************************************
  23.  
  24. Firebug.HTMLModule = extend(Firebug.Module,
  25. {
  26.     initialize: function(prefDomain, prefNames)
  27.     {
  28.         Firebug.Module.initialize.apply(this, arguments);
  29.         Firebug.Debugger.addListener(this.DebuggerListener);
  30.     },
  31.  
  32.     initContext: function(context, persistedState)
  33.     {
  34.         Firebug.Module.initContext.apply(this, arguments);
  35.         context.mutationBreakpoints = new MutationBreakpointGroup();
  36.     },
  37.  
  38.     loadedContext: function(context, persistedState)
  39.     {
  40.         context.mutationBreakpoints.load(context);
  41.     },
  42.  
  43.     destroyContext: function(context, persistedState)
  44.     {
  45.         Firebug.Module.destroyContext.apply(this, arguments);
  46.  
  47.         context.mutationBreakpoints.store(context);
  48.     },
  49.  
  50.     shutdown: function()
  51.     {
  52.         Firebug.Module.shutdown.apply(this, arguments);
  53.         Firebug.Debugger.removeListener(this.DebuggerListener);
  54.     },
  55.  
  56.     deleteNode: function(node, context)
  57.     {
  58.         dispatch(this.fbListeners, "onBeginFirebugChange", [node, context]);
  59.         node.parentNode.removeChild(node);
  60.         dispatch(this.fbListeners, "onEndFirebugChange", [node, context]);
  61.     },
  62.  
  63.     deleteAttribute: function(node, attr, context)
  64.     {
  65.         dispatch(this.fbListeners, "onBeginFirebugChange", [node, context]);
  66.         node.removeAttribute(attr);
  67.         dispatch(this.fbListeners, "onEndFirebugChange", [node, context]);
  68.     }
  69. });
  70.  
  71. // ************************************************************************************************
  72.  
  73. Firebug.HTMLPanel = function() {};
  74.  
  75. Firebug.HTMLPanel.prototype = extend(Firebug.Panel,
  76. {
  77.     toggleEditing: function()
  78.     {
  79.         if (this.editing)
  80.             Firebug.Editor.stopEditing();
  81.         else
  82.             this.editNode(this.selection);
  83.     },
  84.  
  85.     resetSearch: function()
  86.     {
  87.         delete this.lastSearch;
  88.     },
  89.  
  90.     selectNext: function()
  91.     {
  92.         var objectBox = this.ioBox.createObjectBox(this.selection);
  93.         var next = this.ioBox.getNextObjectBox(objectBox);
  94.         if (next)
  95.         {
  96.             this.select(next.repObject);
  97.  
  98.             if (Firebug.Inspector.inspecting)
  99.                 Firebug.Inspector.inspectNode(next.repObject);
  100.  
  101.         }
  102.     },
  103.  
  104.     selectPrevious: function()
  105.     {
  106.         var objectBox = this.ioBox.createObjectBox(this.selection);
  107.         var previous = this.ioBox.getPreviousObjectBox(objectBox);
  108.         if (previous)
  109.         {
  110.             this.select(previous.repObject);
  111.  
  112.             if (Firebug.Inspector.inspecting)
  113.                 Firebug.Inspector.inspectNode(previous.repObject);
  114.         }
  115.     },
  116.  
  117.     selectNodeBy: function(dir)
  118.     {
  119.         if (dir == "up")
  120.             this.selectPrevious();
  121.         else if (dir == "down")
  122.             this.selectNext();
  123.         else if (dir == "left")
  124.         {
  125.             var box = this.ioBox.createObjectBox(this.selection);
  126.             if (!hasClass(box, "open"))
  127.                 this.select(this.ioBox.getParentObjectBox(box).repObject);
  128.             else
  129.                 this.ioBox.contractObject(this.selection);
  130.         }
  131.         else if (dir == "right")
  132.         {
  133.             var box = this.ioBox.createObjectBox(this.selection);
  134.             if (!hasClass(box, "open"))
  135.                 this.ioBox.expandObject(this.selection);
  136.             else
  137.                 this.selectNext();
  138.         }
  139.         Firebug.Inspector.highlightObject(this.selection, this.context);
  140.     },
  141.  
  142.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  143.  
  144.     editNewAttribute: function(elt)
  145.     {
  146.         var objectNodeBox = this.ioBox.findObjectBox(elt);
  147.         if (objectNodeBox)
  148.         {
  149.             var labelBox = objectNodeBox.firstChild.lastChild;
  150.             var bracketBox = labelBox.getElementsByClassName("nodeBracket").item(0);
  151.             Firebug.Editor.insertRow(bracketBox, "before");
  152.         }
  153.     },
  154.  
  155.     editAttribute: function(elt, attrName)
  156.     {
  157.         var objectNodeBox = this.ioBox.findObjectBox(elt);
  158.         if (objectNodeBox)
  159.         {
  160.             var attrBox = HTMLLib.findNodeAttrBox(objectNodeBox, attrName);
  161.             if (attrBox)
  162.             {
  163.                 var attrValueBox = attrBox.childNodes[3];
  164.                 var value = elt.getAttribute(attrName);
  165.                 Firebug.Editor.startEditing(attrValueBox, value);
  166.             }
  167.         }
  168.     },
  169.  
  170.     deleteAttribute: function(elt, attrName)
  171.     {
  172.         Firebug.HTMLModule.deleteAttribute(elt, attrName, this.context);
  173.     },
  174.  
  175.     localEditors:{}, // instantiated editor cache
  176.     editNode: function(node)
  177.     {
  178.         var objectNodeBox = this.ioBox.findObjectBox(node);
  179.         if (objectNodeBox)
  180.         {
  181.             var type = getElementType(node);
  182.             var editor = this.localEditors[type];
  183.             if (!editor)
  184.             {
  185.              // look for special purpose editor (inserted by an extension), otherwise use our html editor
  186.                 var specializedEditor = Firebug.HTMLPanel.Editors[type] || Firebug.HTMLPanel.Editors['html'];
  187.                 editor = this.localEditors[type] = new specializedEditor(this.document);
  188.             }
  189.             this.startEditingNode(node, objectNodeBox, editor, type);
  190.         }
  191.     },
  192.  
  193.     startEditingNode: function(node, box, editor, type)
  194.     {
  195.         switch (type)
  196.         {
  197.             case 'html':
  198.             case 'xhtml':
  199.                 this.startEditingHTMLNode(node, box, editor);
  200.                 break;
  201.             default:
  202.                 this.startEditingXMLNode(node, box, editor);
  203.         }
  204.     },
  205.  
  206.     startEditingXMLNode: function(node, box, editor)
  207.     {
  208.         var xml = getElementXML(node);
  209.         Firebug.Editor.startEditing(box, xml, editor);
  210.     },
  211.  
  212.     startEditingHTMLNode: function(node, box, editor)
  213.     {
  214.         if ( nonEditableTags.hasOwnProperty(node.localName) )
  215.             return;
  216.         editor.innerEditMode = node.localName in innerEditableTags;
  217.  
  218.         var html = editor.innerEditMode ? node.innerHTML : getElementHTML(node);
  219.         Firebug.Editor.startEditing(box, html, editor);
  220.     },
  221.  
  222.     deleteNode: function(node, dir)
  223.     {
  224.         dir = dir || 'up';
  225.         var box = this.ioBox.createObjectBox(node);
  226.         if (hasClass(box, "open"))
  227.             this.ioBox.contractObject(this.selection);
  228.         this.selectNodeBy(dir);
  229.         Firebug.HTMLModule.deleteNode(node, this.context);
  230.     },
  231.  
  232.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  233.  
  234.     getElementSourceText: function(node)
  235.     {
  236.         if (this.sourceElements)
  237.         {
  238.             var index = this.sourceElementNodes.indexOf(node);
  239.             if (index != -1)
  240.                 return this.sourceElements[index];
  241.         }
  242.  
  243.         var lines;
  244.  
  245.         var url = HTMLLib.getSourceHref(node);
  246.         if (url)
  247.             lines = this.context.sourceCache.load(url);
  248.         else
  249.         {
  250.             var text = HTMLLib.getSourceText(node);
  251.             lines = splitLines(text);
  252.         }
  253.  
  254.         var sourceElt = new SourceText(lines, node);
  255.  
  256.         if (!this.sourceElements)
  257.         {
  258.             this.sourceElements =  [sourceElt];
  259.             this.sourceElementNodes = [node];
  260.         }
  261.         else
  262.         {
  263.             this.sourceElements.push(sourceElt);
  264.             this.sourceElementNodes.push(node);
  265.         }
  266.  
  267.         return sourceElt;
  268.     },
  269.  
  270.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  271.  
  272.     mutateAttr: function(target, attrChange, attrName, attrValue)
  273.     {
  274.         // Every time the user scrolls we get this pointless mutation event, which
  275.         // is only bad for performance
  276.         if (attrName == "curpos")
  277.             return;
  278.  
  279.         // Due to the delay call this may or may not exist in the tree anymore
  280.         if (!this.ioBox.isInExistingRoot(target))
  281.         {
  282.             return;
  283.         }
  284.  
  285.         this.markChange();
  286.  
  287.         var objectNodeBox = Firebug.scrollToMutations || Firebug.expandMutations
  288.             ? this.ioBox.createObjectBox(target)
  289.             : this.ioBox.findObjectBox(target);
  290.  
  291.         if (!objectNodeBox)
  292.             return;
  293.  
  294.         if (isVisible(objectNodeBox.repObject))
  295.             removeClass(objectNodeBox, "nodeHidden");
  296.         else
  297.             setClass(objectNodeBox, "nodeHidden");
  298.  
  299.         if (attrChange == MODIFICATION || attrChange == ADDITION)
  300.         {
  301.             var nodeAttr = HTMLLib.findNodeAttrBox(objectNodeBox, attrName);
  302.             if (nodeAttr && nodeAttr.childNodes.length > 3)
  303.             {
  304.                 var attrValueBox = nodeAttr.childNodes[3];
  305.                 var attrValueText = nodeAttr.childNodes[3].firstChild;
  306.                 if (attrValueText)
  307.                     attrValueText.nodeValue = attrValue;
  308.  
  309.                 this.highlightMutation(attrValueBox, objectNodeBox, "mutated");
  310.             }
  311.             else
  312.             {
  313.                 var attr = target.getAttributeNode(attrName);
  314.                 if (attr)
  315.                 {
  316.                     var nodeAttr = Firebug.HTMLPanel.AttrNode.tag.replace({attr: attr},
  317.                             this.document);
  318.  
  319.                     var labelBox = objectNodeBox.firstChild.lastChild;
  320.                     var bracketBox = labelBox.getElementsByClassName("nodeBracket").item(0);
  321.                     labelBox.insertBefore(nodeAttr, bracketBox);
  322.  
  323.                     this.highlightMutation(nodeAttr, objectNodeBox, "mutated");
  324.                 }
  325.             }
  326.         }
  327.         else if (attrChange == REMOVAL)
  328.         {
  329.             var nodeAttr = HTMLLib.findNodeAttrBox(objectNodeBox, attrName);
  330.             if (nodeAttr)
  331.             {
  332.                 nodeAttr.parentNode.removeChild(nodeAttr);
  333.             }
  334.  
  335.             // We want to highlight regardless as the domplate may have been
  336.             // generated after the attribute was removed from the node
  337.             this.highlightMutation(objectNodeBox, objectNodeBox, "mutated");
  338.         }
  339.     },
  340.  
  341.     mutateText: function(target, parent, textValue)
  342.     {
  343.         // Due to the delay call this may or may not exist in the tree anymore
  344.         if (!this.ioBox.isInExistingRoot(target))
  345.         {
  346.             return;
  347.         }
  348.  
  349.         this.markChange();
  350.  
  351.         var parentNodeBox = Firebug.scrollToMutations || Firebug.expandMutations
  352.             ? this.ioBox.createObjectBox(parent)
  353.             : this.ioBox.findObjectBox(parent);
  354.  
  355.         if (!parentNodeBox)
  356.         {
  357.             return;
  358.         }
  359.  
  360.         if (!Firebug.showFullTextNodes)
  361.             textValue = cropMultipleLines(textValue);
  362.  
  363.         var parentTag = getNodeBoxTag(parentNodeBox);
  364.         if (parentTag == Firebug.HTMLPanel.TextElement.tag)
  365.         {
  366.             var nodeText = HTMLLib.getTextElementTextBox(parentNodeBox);
  367.             if (!nodeText.firstChild)
  368.             {
  369.                 return;
  370.             }
  371.  
  372.             nodeText.firstChild.nodeValue = textValue;
  373.  
  374.             this.highlightMutation(nodeText, parentNodeBox, "mutated");
  375.         }
  376.         else
  377.         {
  378.             var childBox = this.ioBox.getChildObjectBox(parentNodeBox);
  379.             if (!childBox)
  380.             {
  381.                 return;
  382.             }
  383.  
  384.             var textNodeBox = this.ioBox.findChildObjectBox(childBox, target);
  385.             if (textNodeBox)
  386.             {
  387.                 // structure for comment and cdata. Are there others?
  388.                 textNodeBox.children[0].firstChild.nodeValue = textValue;
  389.  
  390.                 this.highlightMutation(textNodeBox, parentNodeBox, "mutated");
  391.             }
  392.             else if (Firebug.scrollToMutations || Firebug.expandMutations)
  393.             {
  394.                 // We are not currently rendered but we are set to highlight
  395.                 var objectBox = this.ioBox.createObjectBox(target);
  396.                 this.highlightMutation(objectBox, objectBox, "mutated");
  397.             }
  398.         }
  399.     },
  400.  
  401.     mutateNode: function(target, parent, nextSibling, removal)
  402.     {
  403.         if (!removal && !this.ioBox.isInExistingRoot(target))
  404.         {
  405.             return;
  406.         }
  407.  
  408.         this.markChange();  // This invalidates the panels for every mutate
  409.  
  410.         var parentNodeBox = Firebug.scrollToMutations || Firebug.expandMutations
  411.             ? this.ioBox.createObjectBox(parent)
  412.             : this.ioBox.findObjectBox(parent);
  413.  
  414.         if (!parentNodeBox)
  415.             return;
  416.  
  417.         if (!Firebug.showTextNodesWithWhitespace && this.isWhitespaceText(target))
  418.             return;
  419.  
  420.         // target is only whitespace
  421.  
  422.         var newParentTag = getNodeTag(parent);
  423.         var oldParentTag = getNodeBoxTag(parentNodeBox);
  424.  
  425.         if (newParentTag == oldParentTag)
  426.         {
  427.             if (parentNodeBox.populated)
  428.             {
  429.                 if (removal)
  430.                 {
  431.                     this.ioBox.removeChildBox(parentNodeBox, target);
  432.  
  433.                     this.highlightMutation(parentNodeBox, parentNodeBox, "mutated");
  434.                 }
  435.                 else
  436.                 {
  437.                     if (nextSibling)
  438.                     {
  439.                         while (
  440.                                 (!Firebug.showTextNodesWithWhitespace && Firebug.HTMLLib.isWhitespaceText(nextSibling)) ||
  441.                                 (!Firebug.showCommentNodes && nextSibling instanceof Comment)
  442.                               )
  443.                         {
  444.                             nextSibling = this.findNextSibling(nextSibling);
  445.                         }
  446.                     }
  447.  
  448.                     var objectBox = nextSibling
  449.                         ? this.ioBox.insertChildBoxBefore(parentNodeBox, target, nextSibling)
  450.                         : this.ioBox.appendChildBox(parentNodeBox, target);
  451.  
  452.                     this.highlightMutation(objectBox, objectBox, "mutated");
  453.                 }
  454.             }
  455.             else // !parentNodeBox.populated
  456.             {
  457.                 var newParentNodeBox = newParentTag.replace({object: parent}, this.document);
  458.                 parentNodeBox.parentNode.replaceChild(newParentNodeBox, parentNodeBox);
  459.  
  460.                 if (this.selection && (!this.selection.parentNode || parent == this.selection))
  461.                     this.ioBox.select(parent, true);
  462.  
  463.                 this.highlightMutation(newParentNodeBox, newParentNodeBox, "mutated");
  464.  
  465.                 if (!removal && (Firebug.scrollToMutations || Firebug.expandMutations))
  466.                 {
  467.                     var objectBox = this.ioBox.createObjectBox(target);
  468.                     this.highlightMutation(objectBox, objectBox, "mutated");
  469.                 }
  470.             }
  471.         }
  472.         else // newParentTag != oldParentTag
  473.         {
  474.             var newParentNodeBox = newParentTag.replace({object: parent}, this.document);
  475.             if (parentNodeBox.parentNode)
  476.                 parentNodeBox.parentNode.replaceChild(newParentNodeBox, parentNodeBox);
  477.  
  478.             if (hasClass(parentNodeBox, "open"))
  479.                 this.ioBox.toggleObjectBox(newParentNodeBox, true);
  480.  
  481.             if (this.selection && (!this.selection.parentNode || parent == this.selection))
  482.                 this.ioBox.select(parent, true);
  483.  
  484.             this.highlightMutation(newParentNodeBox, newParentNodeBox, "mutated");
  485.  
  486.             if (!removal && (Firebug.scrollToMutations || Firebug.expandMutations))
  487.             {
  488.                 var objectBox = this.ioBox.createObjectBox(target);
  489.                 this.highlightMutation(objectBox, objectBox, "mutated");
  490.             }
  491.         }
  492.     },
  493.  
  494.     highlightMutation: function(elt, objectBox, type)
  495.     {
  496.         if (!elt)
  497.             return;
  498.  
  499.         if (Firebug.scrollToMutations || Firebug.expandMutations)
  500.         {
  501.             if (this.context.mutationTimeout)
  502.             {
  503.                 this.context.clearTimeout(this.context.mutationTimeout);
  504.                 delete this.context.mutationTimeout;
  505.             }
  506.  
  507.             var ioBox = this.ioBox;
  508.             var panelNode = this.panelNode;
  509.  
  510.             this.context.mutationTimeout = this.context.setTimeout(function()
  511.             {
  512.                 ioBox.openObjectBox(objectBox);
  513.  
  514.                 if (Firebug.scrollToMutations)
  515.                     scrollIntoCenterView(objectBox, panelNode);
  516.             }, 200);
  517.         }
  518.  
  519.         if (Firebug.highlightMutations)
  520.             setClassTimed(elt, type, this.context);
  521.     },
  522.  
  523.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  524.     // SourceBox proxy
  525.  
  526.     createObjectBox: function(object, isRoot)
  527.     {
  528.         var tag = getNodeTag(object);
  529.         if (tag)
  530.             return tag.replace({object: object}, this.document);
  531.     },
  532.  
  533.     getParentObject: function(node)
  534.     {
  535.         if (node instanceof SourceText)
  536.             return node.owner;
  537.  
  538.         var parentNode = node ? node.parentNode : null;
  539.  
  540.         if (parentNode)
  541.         {
  542.  
  543.             if (parentNode.nodeType == 9) // then parentNode is Document element
  544.             {
  545.                 if (parentNode.defaultView)
  546.                 {
  547.                     if (parentNode.defaultView == this.context.window) // for chromebug to avoid climbing put to browser.xul
  548.                         return null;
  549.  
  550.                     return parentNode.defaultView.frameElement;
  551.                 }
  552.                 else if (this.embeddedBrowserParents)
  553.                 {
  554.                     var skipParent = this.embeddedBrowserParents[node];  // better be HTML element, could be iframe
  555.                     if (skipParent)
  556.                         return skipParent;
  557.                 }
  558.                 else // parent is document element, but no window at defaultView.
  559.                     return null;
  560.             }
  561.             else if (!parentNode.localName)
  562.             {
  563.                 return null;
  564.             }
  565.             else
  566.                 return parentNode;
  567.         }
  568.         else  // Documents have no parentNode; Attr, Document, DocumentFragment, Entity, and Notation. top level windows have no parentNode
  569.         {
  570.             if (node && node.nodeType == 9) // document type
  571.             {
  572.                 if (node.defaultView) // generally a reference to the window object for the document, however that is not defined in the specification
  573.                 {
  574.                     var embeddingFrame = node.defaultView.frameElement;
  575.                     if (embeddingFrame)
  576.                         return embeddingFrame.parentNode;
  577.                 }
  578.                 else // a Document object without a parentNode or window
  579.                     return null;  // top level has no parent
  580.             }
  581.         }
  582.     },
  583.  
  584.     getChildObject: function(node, index, previousSibling)
  585.     {
  586.         if (!node)
  587.         {
  588.             FBTrace.sysout("getChildObject: null node");
  589.             return;
  590.         }
  591.         if (this.isSourceElement(node))
  592.         {
  593.             if (index == 0)
  594.                 return this.getElementSourceText(node);
  595.             else
  596.                 return null;  // no siblings of source elements
  597.         }
  598.         else if (node.contentDocument)  // then the node is a frame
  599.         {
  600.             if (index == 0)
  601.             {
  602.                 if (!this.embeddedBrowserParents)
  603.                     this.embeddedBrowserParents = {};
  604.                 var skipChild = node.contentDocument.documentElement; // unwrap
  605.                 this.embeddedBrowserParents[skipChild] = node;
  606.  
  607.                 return skipChild;  // (the node's).(type 9 document).(HTMLElement)
  608.             }
  609.             else
  610.                 return null;
  611.         }
  612.         else if (node.getSVGDocument && node.getSVGDocument())  // then the node is a frame
  613.         {
  614.             if (index == 0)
  615.             {
  616.                 if (!this.embeddedBrowserParents)
  617.                     this.embeddedBrowserParents = {};
  618.                 var skipChild = node.getSVGDocument().documentElement; // unwrap
  619.                 this.embeddedBrowserParents[skipChild] = node;
  620.  
  621.                 return skipChild;  // (the node's).(type 9 document).(SVGElement)
  622.             }
  623.             else
  624.                 return null;
  625.         }
  626.  
  627.         if (previousSibling)  // then we are walking
  628.             var child = this.getNextSibling(previousSibling);  // may return null, meaning done with iteration.
  629.         else
  630.             var child = this.getFirstChild(node); // child is set to at the beginning of an iteration.
  631.  
  632.         if (Firebug.showTextNodesWithWhitespace)  // then the index is true to the node list
  633.             return child;
  634.         else
  635.         {
  636.             for (; child; child = this.getNextSibling(child))
  637.             {
  638.                 if (!this.isWhitespaceText(child))
  639.                     return child;
  640.             }
  641.         }
  642.         return null;  // we have no children worth showing.
  643.     },
  644.  
  645.     isWhitespaceText: function(node)
  646.     {
  647.         return HTMLLib.isWhitespaceText(node);
  648.     },
  649.  
  650.     getFirstChild: function(node)
  651.     {
  652.         this.treeWalker = node.ownerDocument.createTreeWalker(
  653.                  node, NodeFilter.SHOW_ALL, null, false);
  654.         return this.treeWalker.firstChild();
  655.     },
  656.  
  657.     getNextSibling: function(node)
  658.     {
  659.         var next = this.treeWalker.nextSibling();
  660.  
  661.         if (!next)
  662.             delete this.treeWalker;
  663.  
  664.         return next;
  665.     },
  666.  
  667.     findNextSibling: function (node)
  668.     {
  669.         return HTMLLib.findNextSibling(node);
  670.     },
  671.  
  672.     isSourceElement: function(element)
  673.     {
  674.         return HTMLLib.isSourceElement(element);
  675.     },
  676.  
  677.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  678.     // Events
  679.  
  680.     onMutateAttr: function(event)
  681.     {
  682.         var target = event.target;
  683.         if (unwrapObject(target).firebugIgnore)
  684.             return;
  685.  
  686.         var attrChange = event.attrChange;
  687.         var attrName = event.attrName;
  688.         var newValue = event.newValue;
  689.  
  690.         this.context.delay(function()
  691.         {
  692.             this.mutateAttr(target, attrChange, attrName, newValue);
  693.         }, this);
  694.  
  695.         Firebug.HTMLModule.MutationBreakpoints.onMutateAttr(event, this.context);
  696.     },
  697.  
  698.     onMutateText: function(event)
  699.     {
  700.         var target = event.target;
  701.         var parent = target.parentNode;
  702.  
  703.         var newValue = event.newValue;
  704.  
  705.         this.context.delay(function()
  706.         {
  707.             this.mutateText(target, parent, newValue);
  708.         }, this);
  709.  
  710.         Firebug.HTMLModule.MutationBreakpoints.onMutateText(event, this.context);
  711.     },
  712.  
  713.     onMutateNode: function(event)
  714.     {
  715.         var target = event.target;
  716.         if (unwrapObject(target).firebugIgnore)
  717.             return;
  718.  
  719.         var parent = event.relatedNode;
  720.         var removal = event.type == "DOMNodeRemoved";
  721.         var nextSibling = removal ? null : this.findNextSibling(target);
  722.  
  723.         this.context.delay(function()
  724.         {
  725.             try
  726.             {
  727.                  this.mutateNode(target, parent, nextSibling, removal);
  728.             }
  729.             catch (exc)
  730.             {
  731.             }
  732.         }, this);
  733.  
  734.         Firebug.HTMLModule.MutationBreakpoints.onMutateNode(event, this.context);
  735.     },
  736.  
  737.     onClick: function(event)
  738.     {
  739.         if (isLeftClick(event) && event.detail == 2)
  740.         {
  741.             this.toggleNode(event);
  742.         }
  743.         else if (isAltClick(event) && event.detail == 2 && !this.editing)
  744.         {
  745.             this.editNode(this.selection);
  746.         }
  747.     },
  748.  
  749.     onMouseDown: function(event)
  750.     {
  751.         if (!isLeftClick(event))
  752.             return;
  753.         if (getAncestorByClass(event.target, "nodeTag"))
  754.         {
  755.             var node = Firebug.getRepObject(event.target);
  756.             this.noScrollIntoView = true;
  757.             this.select(node);
  758.             delete this.noScrollIntoView;
  759.             if (hasClass(event.target, "twisty"))
  760.                 this.toggleNode(event);
  761.         }
  762.     },
  763.  
  764.     toggleNode: function(event)
  765.     {
  766.         var node = Firebug.getRepObject(event.target);
  767.         var box = this.ioBox.createObjectBox(node);
  768.         if (!hasClass(box, "open"))
  769.             this.ioBox.expandObject(node);
  770.         else
  771.             this.ioBox.contractObject(this.selection);
  772.     },
  773.  
  774.     onKeyPress: function(event)
  775.     {
  776.         if (this.editing || isControl(event) || isShift(event))
  777.             return;
  778.  
  779.         var node = this.selection;
  780.         if (!node)
  781.             return;
  782.         if (event.keyCode == KeyEvent.DOM_VK_UP)
  783.             this.selectNodeBy("up");
  784.         else if (event.keyCode == KeyEvent.DOM_VK_DOWN)
  785.             this.selectNodeBy("down");
  786.         else if (event.keyCode == KeyEvent.DOM_VK_LEFT)
  787.             this.selectNodeBy("left");
  788.         else if (event.keyCode == KeyEvent.DOM_VK_RIGHT)
  789.             this.selectNodeBy("right");
  790.         else if (event.keyCode == KeyEvent.DOM_VK_BACK_SPACE && !(node.localName in innerEditableTags) && !(nonEditableTags.hasOwnProperty(node.localName)))
  791.             this.deleteNode(node, "up");
  792.         else if (event.keyCode == KeyEvent.DOM_VK_DELETE && !(node.localName in innerEditableTags) && !(nonEditableTags.hasOwnProperty(node.localName)))
  793.             this.deleteNode(node, "down");
  794.         else
  795.             return;
  796.  
  797.         cancelEvent(event);
  798.     },
  799.  
  800.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  801.     // extends Panel
  802.  
  803.     name: "html",
  804.     searchable: true,
  805.     breakable: true,
  806.     dependents: ["css", "computed", "layout", "dom", "domSide", "watch"],
  807.     inspectorHistory: new Array(5),
  808.  
  809.     initialize: function()
  810.     {
  811.         this.onMutateText = bind(this.onMutateText, this);
  812.         this.onMutateAttr = bind(this.onMutateAttr, this);
  813.         this.onMutateNode = bind(this.onMutateNode, this);
  814.         this.onClick = bind(this.onClick, this);
  815.         this.onMouseDown = bind(this.onMouseDown, this);
  816.         this.onKeyPress = bind(this.onKeyPress, this);
  817.  
  818.         Firebug.Panel.initialize.apply(this, arguments);
  819.     },
  820.  
  821.     destroy: function(state)
  822.     {
  823.         persistObjects(this, state);
  824.  
  825.         Firebug.Panel.destroy.apply(this, arguments);
  826.     },
  827.  
  828.     initializeNode: function(oldPanelNode)
  829.     {
  830.         if (!this.ioBox)
  831.             this.ioBox = new InsideOutBox(this, this.panelNode);
  832.  
  833.         this.panelNode.addEventListener("click", this.onClick, false);
  834.         this.panelNode.addEventListener("mousedown", this.onMouseDown, false);
  835.         dispatch([Firebug.A11yModel], "onInitializeNode", [this]);
  836.     },
  837.  
  838.     destroyNode: function()
  839.     {
  840.         this.panelNode.removeEventListener("click", this.onClick, false);
  841.         this.panelNode.removeEventListener("mousedown", this.onMouseDown, false);
  842.         this.panelNode.ownerDocument.removeEventListener("keypress", this.onKeyPress, true);
  843.  
  844.         if (this.ioBox)
  845.         {
  846.             this.ioBox.destroy();
  847.             delete this.ioBox;
  848.         }
  849.         dispatch([Firebug.A11yModel], "onDestroyNode", [this]);
  850.     },
  851.  
  852.     show: function(state)
  853.     {
  854.         this.showToolbarButtons("fbHTMLButtons", true);
  855.  
  856.         this.panelNode.ownerDocument.addEventListener("keypress", this.onKeyPress, true);
  857.  
  858.         if (this.context.loaded)
  859.         {
  860.             if (!this.context.attachedMutation)
  861.             {
  862.                 this.context.attachedMutation = true;
  863.  
  864.                 iterateWindows(this.context.window, bind(function(win)
  865.                 {
  866.                     var doc = win.document;
  867.                     doc.addEventListener("DOMAttrModified", this.onMutateAttr, false);
  868.                     doc.addEventListener("DOMCharacterDataModified", this.onMutateText, false);
  869.                     doc.addEventListener("DOMNodeInserted", this.onMutateNode, false);
  870.                     doc.addEventListener("DOMNodeRemoved", this.onMutateNode, false);
  871.                 }, this));
  872.             }
  873.  
  874.             restoreObjects(this, state);
  875.         }
  876.     },
  877.  
  878.     hide: function()
  879.     {
  880.         this.showToolbarButtons("fbHTMLButtons", false);
  881.         delete this.infoTipURL;  // clear the state that is tracking the infotip so it is reset after next show()
  882.         this.panelNode.ownerDocument.removeEventListener("keypress", this.onKeyPress, true);
  883.     },
  884.  
  885.     watchWindow: function(win)
  886.     {
  887.         if (this.context.window && this.context.window != win) // then I guess we are an embedded window
  888.         {
  889.             var htmlPanel = this;
  890.             iterateWindows(this.context.window, function(subwin)
  891.             {
  892.                 if (win == subwin)
  893.                 {
  894.                     htmlPanel.mutateDocumentEmbedded(win, false);
  895.                 }
  896.             });
  897.  
  898.         }
  899.         if (this.context.attachedMutation)
  900.         {
  901.             var doc = win.document;
  902.             doc.addEventListener("DOMAttrModified", this.onMutateAttr, false);
  903.             doc.addEventListener("DOMCharacterDataModified", this.onMutateText, false);
  904.             doc.addEventListener("DOMNodeInserted", this.onMutateNode, false);
  905.             doc.addEventListener("DOMNodeRemoved", this.onMutateNode, false);
  906.         }
  907.     },
  908.  
  909.     unwatchWindow: function(win)
  910.     {
  911.         if (this.context.window && this.context.window != win) // then I guess we are an embedded window
  912.         {
  913.             var htmlPanel = this;
  914.             iterateWindows(this.context.window, function(subwin)
  915.             {
  916.                 if (win == subwin)
  917.                 {
  918.                     htmlPanel.mutateDocumentEmbedded(win, true);
  919.                 }
  920.             });
  921.  
  922.         }
  923.         var doc = win.document;
  924.         doc.removeEventListener("DOMAttrModified", this.onMutateAttr, false);
  925.         doc.removeEventListener("DOMCharacterDataModified", this.onMutateText, false);
  926.         doc.removeEventListener("DOMNodeInserted", this.onMutateNode, false);
  927.         doc.removeEventListener("DOMNodeRemoved", this.onMutateNode, false);
  928.     },
  929.  
  930.     mutateDocumentEmbedded: function(win, remove)
  931.     {
  932.         // document.documentElement    Returns the Element that is a direct child of document. For HTML documents, this normally the HTML element.
  933.         var target = win.document.documentElement;
  934.         var parent = win.frameElement;
  935.         var nextSibling = this.findNextSibling(target || parent);
  936.         this.mutateNode(target, parent, nextSibling, remove);
  937.     },
  938.  
  939.     supportsObject: function(object)
  940.     {
  941.         if (object instanceof Element || object instanceof Text || object instanceof CDATASection)
  942.             return 2;
  943.         else if (object instanceof SourceLink && object.type == "css" && !reCSS.test(object.href))
  944.             return 2;
  945.         else
  946.             return 0;
  947.     },
  948.  
  949.     updateOption: function(name, value)
  950.     {
  951.         var viewOptionNames = {
  952.                 showCommentNodes:1,
  953.                 showTextNodesWithEntities:1,
  954.                 showTextNodesWithWhitespace:1,
  955.                 showFullTextNodes:1
  956.         };
  957.         if (name in viewOptionNames)
  958.         {
  959.             this.resetSearch();
  960.             clearNode(this.panelNode);
  961.             if (this.ioBox)
  962.                 this.ioBox.destroy();
  963.  
  964.             this.ioBox = new InsideOutBox(this, this.panelNode);
  965.             this.ioBox.select(this.selection, true, true);
  966.         }
  967.     },
  968.  
  969.     updateSelection: function(object)
  970.     {
  971.         if (this.ioBox.sourceRow)
  972.             this.ioBox.sourceRow.removeAttribute("exe_line");
  973.  
  974.         if (object instanceof SourceLink) // && object.type == "css" and !reCSS(object.href) by supports
  975.         {
  976.             var sourceLink = object;
  977.             var stylesheet = getStyleSheetByHref(sourceLink.href, this.context);
  978.             if (stylesheet)
  979.             {
  980.                 var ownerNode = stylesheet.ownerNode;
  981.                 if (ownerNode)
  982.                 {
  983.                     var objectbox = this.ioBox.select(ownerNode, true, true, this.noScrollIntoView);
  984.  
  985.                     // XXXjjb seems like this could be bad for errors at the end of long files
  986.                     //
  987.                     var sourceRow = objectbox.getElementsByClassName("sourceRow").item(0); // first source row in style
  988.                     for (var lineNo = 1; lineNo < sourceLink.line; lineNo++)
  989.                     {
  990.                         if (!sourceRow) break;
  991.                         sourceRow = FBL.getNextByClass(sourceRow,  "sourceRow");
  992.                     }
  993.                     if (sourceRow)
  994.                     {
  995.                         this.ioBox.sourceRow = sourceRow;
  996.                         this.ioBox.sourceRow.setAttribute("exe_line", "true");
  997.                         scrollIntoCenterView(sourceRow);
  998.                         this.ioBox.selectObjectBox(sourceRow, false);  // sourceRow isn't an objectBox, but the function should work anyway...
  999.                     }
  1000.                 }
  1001.             }
  1002.         }
  1003.         else if (Firebug.Inspector.inspecting)
  1004.         {
  1005.             this.ioBox.highlight(object);
  1006.         }
  1007.         else
  1008.         {
  1009.             Firebug.chrome.getSelectedSidePanel().panelNode.scrollTop = 0;
  1010.             this.ioBox.select(object, true, false, this.noScrollIntoView);
  1011.             this.inspectorHistory.unshift(object);
  1012.             if (this.inspectorHistory.length > 5)
  1013.                 this.inspectorHistory.pop();
  1014.         }
  1015.     },
  1016.  
  1017.     stopInspecting: function(object, cancelled)
  1018.     {
  1019.         if (object != this.inspectorHistory)
  1020.         {
  1021.             // Manage history of selection for later access in the command line.
  1022.             this.inspectorHistory.unshift(object);
  1023.             if (this.inspectorHistory.length > 5)
  1024.                 this.inspectorHistory.pop();
  1025.  
  1026.         }
  1027.  
  1028.         this.ioBox.highlight(null);
  1029.  
  1030.         if (!cancelled)
  1031.             this.ioBox.select(object, true);
  1032.     },
  1033.  
  1034.     search: function(text, reverse)
  1035.     {
  1036.         if (!text)
  1037.             return;
  1038.  
  1039.         var search;
  1040.         if (text == this.searchText && this.lastSearch)
  1041.             search = this.lastSearch;
  1042.         else
  1043.         {
  1044.             var doc = this.context.window.document;
  1045.             search = this.lastSearch = new HTMLLib.NodeSearch(text, doc, this.panelNode, this.ioBox);
  1046.         }
  1047.  
  1048.         var loopAround = search.find(reverse, Firebug.Search.isCaseSensitive(text));
  1049.         if (loopAround)
  1050.         {
  1051.             this.resetSearch();
  1052.             this.search(text, reverse);
  1053.         }
  1054.  
  1055.         return !search.noMatch;
  1056.     },
  1057.  
  1058.     getSearchOptionsMenuItems: function()
  1059.     {
  1060.         return [
  1061.             Firebug.Search.searchOptionMenu("search.Case_Sensitive", "searchCaseSensitive")
  1062.         ];
  1063.     },
  1064.  
  1065.     getDefaultSelection: function()
  1066.     {
  1067.         try
  1068.         {
  1069.             var doc = this.context.window.document;
  1070.             return doc.body ? doc.body : getPreviousElement(doc.documentElement.lastChild);
  1071.         }
  1072.         catch (exc)
  1073.         {
  1074.             return null;
  1075.         }
  1076.     },
  1077.  
  1078.     getObjectPath: function(element)
  1079.     {
  1080.         var path = [];
  1081.         for (; element; element = this.getParentObject(element))
  1082.             path.push(element);
  1083.  
  1084.         return path;
  1085.     },
  1086.  
  1087.     getPopupObject: function(target)
  1088.     {
  1089.         return Firebug.getRepObject(target);
  1090.     },
  1091.  
  1092.     getTooltipObject: function(target)
  1093.     {
  1094.         return null;
  1095.     },
  1096.  
  1097.     getOptionsMenuItems: function()
  1098.     {
  1099.         return [
  1100.             optionMenu("ShowFullText", "showFullTextNodes"),
  1101.             optionMenu("ShowWhitespace", "showTextNodesWithWhitespace"),
  1102.             optionMenu("ShowComments", "showCommentNodes"),
  1103.             optionMenu("ShowTextNodesWithEntities", "showTextNodesWithEntities"),
  1104.             "-",
  1105.             optionMenu("HighlightMutations", "highlightMutations"),
  1106.             optionMenu("ExpandMutations", "expandMutations"),
  1107.             optionMenu("ScrollToMutations", "scrollToMutations"),
  1108.             "-",
  1109.             optionMenu("ShadeBoxModel", "shadeBoxModel"),
  1110.             optionMenu("ShowQuickInfoBox","showQuickInfoBox")
  1111.         ];
  1112.     },
  1113.  
  1114.     getContextMenuItems: function(node, target)
  1115.     {
  1116.         if (!node)
  1117.             return null;
  1118.  
  1119.         var items = [];
  1120.  
  1121.         if (node && node.nodeType == 1)
  1122.         {
  1123.             items.push(
  1124.                 "-",
  1125.                 {label: "NewAttribute", command: bindFixed(this.editNewAttribute, this, node) }
  1126.             );
  1127.  
  1128.             var attrBox = getAncestorByClass(target, "nodeAttr");
  1129.             if (getAncestorByClass(target, "nodeAttr"))
  1130.             {
  1131.                 var attrName = attrBox.childNodes[1].textContent;
  1132.  
  1133.                 items.push(
  1134.                     {label: $STRF("EditAttribute", [attrName]), nol10n: true,
  1135.                         command: bindFixed(this.editAttribute, this, node, attrName) },
  1136.                     {label: $STRF("DeleteAttribute", [attrName]), nol10n: true,
  1137.                         command: bindFixed(this.deleteAttribute, this, node, attrName) }
  1138.                 );
  1139.             }
  1140.  
  1141.             if (!( nonEditableTags.hasOwnProperty(node.localName) ))
  1142.             {
  1143.                 var EditElement = "EditHTMLElement";
  1144.  
  1145.                 if (isElementMathML(node))
  1146.                     EditElement = "EditMathMLElement"
  1147.                 else if (isElementSVG(node))
  1148.                     EditElement = "EditSVGElement";
  1149.  
  1150.                 items.push("-", { label: EditElement, command: bindFixed(this.editNode, this, node)},
  1151.                             { label: "DeleteElement", command: bindFixed(this.deleteNode, this, node), disabled:(node.localName in innerEditableTags)}
  1152.                            );
  1153.             }
  1154.         }
  1155.         else
  1156.         {
  1157.             items.push(
  1158.                 "-",
  1159.                 {label: "EditNode", command: bindFixed(this.editNode, this, node) },
  1160.                 {label: "DeleteNode", command: bindFixed(this.deleteNode, this, node) }
  1161.             );
  1162.         }
  1163.  
  1164.         Firebug.HTMLModule.MutationBreakpoints.getContextMenuItems(
  1165.             this.context,node, target, items);
  1166.  
  1167.         return items;
  1168.     },
  1169.  
  1170.     showInfoTip: function(infoTip, target, x, y)
  1171.     {
  1172.         if (!hasClass(target, "nodeValue"))
  1173.             return;
  1174.  
  1175.         var targetNode = Firebug.getRepObject(target);
  1176.         if (targetNode && targetNode.nodeType == 1 && targetNode.localName.toUpperCase() == "IMG")
  1177.         {
  1178.             var url = targetNode.src;
  1179.             if (url == this.infoTipURL) // This state cleared in hide()
  1180.                 return true;
  1181.  
  1182.             this.infoTipURL = url;
  1183.             return Firebug.InfoTip.populateImageInfoTip(infoTip, url);
  1184.         }
  1185.     },
  1186.  
  1187.     getEditor: function(target, value)
  1188.     {
  1189.         if (hasClass(target, "nodeName") || hasClass(target, "nodeValue") || hasClass(target, "nodeBracket"))
  1190.         {
  1191.             if (!this.attrEditor)
  1192.                 this.attrEditor = new Firebug.HTMLPanel.Editors.Attribute(this.document);
  1193.  
  1194.             return this.attrEditor;
  1195.         }
  1196.         else if (hasClass(target, "nodeComment") || hasClass(target, "nodeCDATA"))
  1197.         {
  1198.             if (!this.textDataEditor)
  1199.                 this.textDataEditor = new Firebug.HTMLPanel.Editors.TextData(this.document);
  1200.  
  1201.             return this.textDataEditor;
  1202.         }
  1203.         else if (hasClass(target, "nodeText"))
  1204.         {
  1205.             if (!this.textNodeEditor)
  1206.                 this.textNodeEditor = new Firebug.HTMLPanel.Editors.TextNode(this.document);
  1207.  
  1208.             return this.textNodeEditor;
  1209.         }
  1210.     },
  1211.  
  1212.     getInspectorVars: function()
  1213.     {
  1214.         var vars = {};
  1215.         for (var i=0; i<2; i++)
  1216.             vars["$"+i] = this.inspectorHistory[i];
  1217.  
  1218.         return vars;
  1219.     },
  1220.  
  1221.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1222.     // Break on Mutate
  1223.  
  1224.     breakOnNext: function(breaking)
  1225.     {
  1226.         Firebug.HTMLModule.MutationBreakpoints.breakOnNext(this.context, breaking);
  1227.     },
  1228.  
  1229.     shouldBreakOnNext: function()
  1230.     {
  1231.         return this.context.breakOnNextMutate;
  1232.     },
  1233.  
  1234.     getBreakOnNextTooltip: function(enabled)
  1235.     {
  1236.         return (enabled ? $STR("html.Disable Break On Mutate") : $STR("html.Break On Mutate"));
  1237.     },
  1238. });
  1239.  
  1240. // ************************************************************************************************
  1241.  
  1242. var AttrTag = Firebug.HTMLPanel.AttrTag =
  1243.     SPAN({"class": "nodeAttr editGroup"},
  1244.         " ", SPAN({"class": "nodeName editable"}, "$attr.nodeName"), "="",
  1245.         SPAN({"class": "nodeValue editable"}, "$attr.nodeValue"), """
  1246.     );
  1247.  
  1248. var TextTag = Firebug.HTMLPanel.TextTag =
  1249.     SPAN({"class": "nodeText editable"},
  1250.         FOR("char", "$object|getNodeTextGroups",
  1251.             SPAN({"class": "$char.class $char.extra"}, "$char.str")
  1252.         )
  1253.     );
  1254.  
  1255. // ************************************************************************************************
  1256.  
  1257. Firebug.HTMLPanel.CompleteElement = domplate(FirebugReps.Element,
  1258. {
  1259.     tag:
  1260.         DIV({"class": "nodeBox open $object|getHidden repIgnore", _repObject: "$object", role : 'presentation'},
  1261.             DIV({"class": "nodeLabel", role: "presentation"},
  1262.                 SPAN({"class": "nodeLabelBox repTarget repTarget", role : 'treeitem', 'aria-expanded' : 'false'},
  1263.                     "<",
  1264.                     SPAN({"class": "nodeTag"}, "$object.nodeName|toLowerCase"),
  1265.                     FOR("attr", "$object|attrIterator", AttrTag),
  1266.                     SPAN({"class": "nodeBracket"}, ">")
  1267.                 )
  1268.             ),
  1269.             DIV({"class": "nodeChildBox", role :"group"},
  1270.                 FOR("child", "$object|childIterator",
  1271.                     TAG("$child|getNodeTag", {object: "$child"})
  1272.                 )
  1273.             ),
  1274.             DIV({"class": "nodeCloseLabel", role:"presentation"},
  1275.                 "</",
  1276.                 SPAN({"class": "nodeTag"}, "$object.nodeName|toLowerCase"),
  1277.                 ">"
  1278.              )
  1279.         ),
  1280.  
  1281.     getNodeTag: function(node)
  1282.     {
  1283.         return getNodeTag(node, true);
  1284.     },
  1285.  
  1286.     childIterator: function(node)
  1287.     {
  1288.         if (node.contentDocument)
  1289.             return [node.contentDocument.documentElement];
  1290.  
  1291.         if (Firebug.showTextNodesWithWhitespace)
  1292.             return cloneArray(node.childNodes);
  1293.         else
  1294.         {
  1295.             var nodes = [];
  1296.             for (var child = node.firstChild; child; child = child.nextSibling)
  1297.             {
  1298.                 if (child.nodeType != Node.TEXT_NODE || !HTMLLib.isWhitespaceText(child))
  1299.                     nodes.push(child);
  1300.             }
  1301.             return nodes;
  1302.         }
  1303.     }
  1304. });
  1305.  
  1306. Firebug.HTMLPanel.SoloElement = domplate(Firebug.HTMLPanel.CompleteElement,
  1307. {
  1308.     tag:
  1309.         DIV({"class": "soloElement", onmousedown: "$onMouseDown"},
  1310.             Firebug.HTMLPanel.CompleteElement.tag
  1311.         ),
  1312.  
  1313.     onMouseDown: function(event)
  1314.     {
  1315.         for (var child = event.target; child; child = child.parentNode)
  1316.         {
  1317.             if (child.repObject)
  1318.             {
  1319.                 var panel = Firebug.getElementPanel(child);
  1320.                 Firebug.chrome.select(child.repObject);
  1321.                 break;
  1322.             }
  1323.         }
  1324.     }
  1325. });
  1326.  
  1327. Firebug.HTMLPanel.Element = domplate(FirebugReps.Element,
  1328. {
  1329.     tag:
  1330.         DIV({"class": "nodeBox containerNodeBox $object|getHidden repIgnore", _repObject: "$object", role :"presentation"},
  1331.             DIV({"class": "nodeLabel", role: "presentation"},
  1332.                 IMG({"class": "twisty", role: "presentation"}),
  1333.                 SPAN({"class": "nodeLabelBox repTarget", role : 'treeitem', 'aria-expanded' : 'false'},
  1334.                     "<",
  1335.                     SPAN({"class": "nodeTag"}, "$object.nodeName|toLowerCase"),
  1336.                     FOR("attr", "$object|attrIterator", AttrTag),
  1337.                     SPAN({"class": "nodeBracket editable insertBefore"}, ">")
  1338.                 )
  1339.             ),
  1340.             DIV({"class": "nodeChildBox", role :"group"}), /* nodeChildBox is special signal in insideOutBox */
  1341.             DIV({"class": "nodeCloseLabel", role : "presentation"},
  1342.                 SPAN({"class": "nodeCloseLabelBox repTarget"},
  1343.                     "</",
  1344.                     SPAN({"class": "nodeTag"}, "$object.nodeName|toLowerCase"),
  1345.                     ">"
  1346.                 )
  1347.             )
  1348.         )
  1349. });
  1350.  
  1351. Firebug.HTMLPanel.TextElement = domplate(FirebugReps.Element,
  1352. {
  1353.     tag:
  1354.         DIV({"class": "nodeBox textNodeBox $object|getHidden repIgnore", _repObject: "$object", role : 'presentation'},
  1355.             DIV({"class": "nodeLabel", role: "presentation"},
  1356.                 SPAN({"class": "nodeLabelBox repTarget", role : 'treeitem'},
  1357.                     "<",
  1358.                     SPAN({"class": "nodeTag"}, "$object.nodeName|toLowerCase"),
  1359.                     FOR("attr", "$object|attrIterator", AttrTag),
  1360.                     SPAN({"class": "nodeBracket editable insertBefore"}, ">"),
  1361.                     TextTag,
  1362.                     "</",
  1363.                     SPAN({"class": "nodeTag"}, "$object.nodeName|toLowerCase"),
  1364.                     ">"
  1365.                 )
  1366.             )
  1367.         )
  1368. });
  1369.  
  1370. Firebug.HTMLPanel.EmptyElement = domplate(FirebugReps.Element,
  1371. {
  1372.     tag:
  1373.         DIV({"class": "nodeBox emptyNodeBox $object|getHidden repIgnore", _repObject: "$object", role : 'presentation'},
  1374.             DIV({"class": "nodeLabel", role: "presentation"},
  1375.                 SPAN({"class": "nodeLabelBox repTarget", role : 'treeitem'},
  1376.                     "<",
  1377.                     SPAN({"class": "nodeTag"}, "$object.nodeName|toLowerCase"),
  1378.                     FOR("attr", "$object|attrIterator", AttrTag),
  1379.                     SPAN({"class": "nodeBracket editable insertBefore"}, ">")
  1380.                 )
  1381.             )
  1382.         )
  1383. });
  1384.  
  1385. Firebug.HTMLPanel.XEmptyElement = domplate(FirebugReps.Element,
  1386. {
  1387.     tag:
  1388.         DIV({"class": "nodeBox emptyNodeBox $object|getHidden repIgnore", _repObject: "$object", role : 'presentation'},
  1389.             DIV({"class": "nodeLabel", role: "presentation"},
  1390.                 SPAN({"class": "nodeLabelBox repTarget", role : 'treeitem'},
  1391.                     "<",
  1392.                     SPAN({"class": "nodeTag"}, "$object.nodeName|toLowerCase"),
  1393.                     FOR("attr", "$object|attrIterator", AttrTag),
  1394.                     SPAN({"class": "nodeBracket editable insertBefore"}, "/>")
  1395.                 )
  1396.             )
  1397.         )
  1398. });
  1399.  
  1400. Firebug.HTMLPanel.AttrNode = domplate(FirebugReps.Element,
  1401. {
  1402.     tag: AttrTag
  1403. });
  1404.  
  1405. Firebug.HTMLPanel.TextNode = domplate(FirebugReps.Element,
  1406. {
  1407.     tag:
  1408.         DIV({"class": "nodeBox", _repObject: "$object", role : 'presentation'},
  1409.             TextTag
  1410.         )
  1411. });
  1412.  
  1413. Firebug.HTMLPanel.CDATANode = domplate(FirebugReps.Element,
  1414. {
  1415.     tag:
  1416.         DIV({"class": "nodeBox", _repObject: "$object", role : 'presentation'},
  1417.             "<![CDATA[",
  1418.             SPAN({"class": "nodeText nodeCDATA editable"}, "$object.nodeValue"),
  1419.             "]]>"
  1420.         )
  1421. });
  1422.  
  1423. Firebug.HTMLPanel.CommentNode = domplate(FirebugReps.Element,
  1424. {
  1425.     tag:
  1426.         DIV({"class": "nodeBox nodeComment", _repObject: "$object", role : 'presentation'},
  1427.             "<!--",
  1428.             SPAN({"class": "nodeComment editable"}, "$object.nodeValue"),
  1429.             "-->"
  1430.         )
  1431. });
  1432.  
  1433.  
  1434. // ************************************************************************************************
  1435. // TextDataEditor
  1436.  
  1437. /*
  1438.  * TextDataEditor deals with text of comments and cdata nodes
  1439.  */
  1440.  
  1441. function TextDataEditor(doc)
  1442. {
  1443.     this.initializeInline(doc);
  1444. }
  1445.  
  1446. TextDataEditor.prototype = domplate(Firebug.InlineEditor.prototype,
  1447. {
  1448.  
  1449.     saveEdit: function(target, value, previousValue)
  1450.     {
  1451.         var node = Firebug.getRepObject(target);
  1452.         if (!node)
  1453.             return;
  1454.         target.data = value;
  1455.         node.data = value;
  1456.     }
  1457. });
  1458.  
  1459. //************************************************************************************************
  1460. // TextNodeEditor
  1461.  
  1462. /*
  1463.  * TextNodeEditor deals with text nodes that do and do not have sibling elements. If
  1464.  * there are no sibling elements, the parent is known as a TextElement. In other cases
  1465.  * we keep track of their position via a range (this is in part because as people type
  1466.  * html, the range will keep track of the text nodes and elements that the user
  1467.  * is creating as they type, and this range could be in the middle of the parent
  1468.  * elements children).
  1469.  */
  1470.  
  1471. function TextNodeEditor(doc)
  1472. {
  1473.     this.initializeInline(doc);
  1474. }
  1475.  
  1476. TextNodeEditor.prototype = domplate(Firebug.InlineEditor.prototype,
  1477. {
  1478.  
  1479.     beginEditing: function(target, value)
  1480.     {
  1481.         var node = Firebug.getRepObject(target);
  1482.         if (!node || node instanceof Element)
  1483.             return;
  1484.         var document = node.ownerDocument;
  1485.         this.range = document.createRange();
  1486.         this.range.setStartBefore(node);
  1487.         this.range.setEndAfter(node);
  1488.     },
  1489.  
  1490.     endEditing: function(target, value, cancel)
  1491.     {
  1492.         if (this.range)
  1493.         {
  1494.             this.range.detach();
  1495.             delete this.range;
  1496.         }
  1497.         // Remove empty groups by default
  1498.         return true;
  1499.     },
  1500.  
  1501.     saveEdit: function(target, value, previousValue)
  1502.     {
  1503.         var node = Firebug.getRepObject(target);
  1504.         if (!node)
  1505.             return;
  1506.         value = unescapeForTextNode(value || '');
  1507.         target.innerHTML = escapeForTextNode(value);
  1508.         if (node instanceof Element)
  1509.         {
  1510.             if (isElementMathML(node) || isElementSVG(node))
  1511.                 node.textContent=value;
  1512.             else
  1513.                 node.innerHTML=value;
  1514.         }
  1515.         else
  1516.         {
  1517.             try
  1518.             {
  1519.                 var documentFragment = this.range.createContextualFragment(value);
  1520.                 var cnl=documentFragment.childNodes.length;
  1521.                 this.range.deleteContents();
  1522.                 this.range.insertNode(documentFragment);
  1523.                 var r = this.range, sc = r.startContainer, so = r.startOffset;
  1524.                 this.range.setEnd(sc,so+cnl);
  1525.             } catch (e) {}
  1526.         }
  1527.     }
  1528. });
  1529.  
  1530. //************************************************************************************************
  1531. //AttributeEditor
  1532.  
  1533. function AttributeEditor(doc)
  1534. {
  1535.     this.initializeInline(doc);
  1536. }
  1537.  
  1538. AttributeEditor.prototype = domplate(Firebug.InlineEditor.prototype,
  1539. {
  1540.     saveEdit: function(target, value, previousValue)
  1541.     {
  1542.         var element = Firebug.getRepObject(target);
  1543.         if (!element)
  1544.             return;
  1545.  
  1546.         // XXXstr unescape value
  1547.  
  1548.         target.innerHTML = escapeForElementAttribute(value);
  1549.  
  1550.         if (hasClass(target, "nodeName"))
  1551.         {
  1552.             if (value != previousValue)
  1553.                 element.removeAttribute(previousValue);
  1554.             if (value)
  1555.             {
  1556.                 var attrValue = getNextByClass(target, "nodeValue").textContent;
  1557.                 element.setAttribute(value, attrValue);
  1558.             }
  1559.             else
  1560.                 element.removeAttribute(value);
  1561.         }
  1562.         else if (hasClass(target, "nodeValue"))
  1563.         {
  1564.             var attrName = getPreviousByClass(target, "nodeName").textContent;
  1565.             element.setAttribute(attrName, value);
  1566.         }
  1567.         //this.panel.markChange();
  1568.     },
  1569.  
  1570.     advanceToNext: function(target, charCode)
  1571.     {
  1572.         if (charCode == 61 && hasClass(target, "nodeName"))
  1573.             return true;
  1574.     },
  1575.  
  1576.     insertNewRow: function(target, insertWhere)
  1577.     {
  1578.         var emptyAttr = {nodeName: "", nodeValue: ""};
  1579.         var sibling = insertWhere == "before" ? target.previousSibling : target;
  1580.         return AttrTag.insertAfter({attr: emptyAttr}, sibling);
  1581.     }
  1582. });
  1583.  
  1584. //************************************************************************************************
  1585. //HTMLEditor
  1586.  
  1587. function HTMLEditor(doc)
  1588. {
  1589.  this.box = this.tag.replace({}, doc, this);
  1590.  this.input = this.box.firstChild;
  1591.  
  1592.  this.multiLine = true;
  1593.  this.tabNavigation = false;
  1594.  this.arrowCompletion = false;
  1595. }
  1596.  
  1597. HTMLEditor.prototype = domplate(Firebug.BaseEditor,
  1598. {
  1599.  tag: DIV(
  1600.      TEXTAREA({"class": "htmlEditor fullPanelEditor", oninput: "$onInput"})
  1601.  ),
  1602.  
  1603.  getValue: function()
  1604.  {
  1605.      return this.input.value;
  1606.  },
  1607.  
  1608.  setValue: function(value)
  1609.  {
  1610.      return this.input.value = value;
  1611.  },
  1612.  
  1613.  show: function(target, panel, value, textSize, targetSize)
  1614.  {
  1615.      this.target = target;
  1616.      this.panel = panel;
  1617.      this.editingElements = [target.repObject, null];
  1618.  
  1619.      this.panel.panelNode.appendChild(this.box);
  1620.  
  1621.      this.input.value = value;
  1622.      this.input.focus();
  1623.  
  1624.      var command = Firebug.chrome.$("cmd_toggleHTMLEditing");
  1625.      command.setAttribute("checked", true);
  1626.  },
  1627.  
  1628.  hide: function()
  1629.  {
  1630.      var command = Firebug.chrome.$("cmd_toggleHTMLEditing");
  1631.      command.setAttribute("checked", false);
  1632.  
  1633.      this.panel.panelNode.removeChild(this.box);
  1634.  
  1635.      delete this.editingElements;
  1636.      delete this.target;
  1637.      delete this.panel;
  1638.  },
  1639.  
  1640.  saveEdit: function(target, value, previousValue)
  1641.  {
  1642.      // Remove all of the nodes in the last range we created, except for
  1643.      // the first one, because setOuterHTML will replace it
  1644.      var first = this.editingElements[0], last = this.editingElements[1];
  1645.      if (last && last != first)
  1646.      {
  1647.          for (var child = first.nextSibling; child;)
  1648.          {
  1649.              var next = child.nextSibling;
  1650.              child.parentNode.removeChild(child);
  1651.              if (child == last)
  1652.                  break;
  1653.              else
  1654.                  child = next;
  1655.          }
  1656.      }
  1657.  
  1658.      // Make sure that we create at least one node here, even if it's just
  1659.      // an empty space, because this code depends on having something to replace
  1660.      if (!value)
  1661.          value = " ";
  1662.  
  1663.      if (this.innerEditMode)
  1664.          this.editingElements[0].innerHTML = value;
  1665.      else
  1666.          this.editingElements = setOuterHTML(this.editingElements[0], value);
  1667.  },
  1668.  
  1669.  endEditing: function()
  1670.  {
  1671.      //this.panel.markChange();
  1672.      return true;
  1673.  },
  1674.  
  1675.  // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1676.  
  1677.  onInput: function()
  1678.  {
  1679.      Firebug.Editor.update();
  1680.  }
  1681. });
  1682.  
  1683.  
  1684. // ************************************************************************************************
  1685. // Editors
  1686.  
  1687. Firebug.HTMLPanel.Editors = {
  1688.     html : HTMLEditor,
  1689.     Attribute : AttributeEditor,
  1690.     TextNode: TextNodeEditor,
  1691.     TextData: TextDataEditor
  1692. };
  1693.  
  1694.  
  1695. // ************************************************************************************************
  1696. // Local Helpers
  1697.  
  1698. function getEmptyElementTag(node)
  1699. {
  1700.     var isXhtml= isElementXHTML(node);
  1701.     if (isXhtml)
  1702.         return Firebug.HTMLPanel.XEmptyElement.tag;
  1703.     else
  1704.         return Firebug.HTMLPanel.EmptyElement.tag;
  1705. }
  1706.  
  1707. function getNodeTag(node, expandAll)
  1708. {
  1709.     if (node instanceof Element)
  1710.     {
  1711.         if (node instanceof HTMLAppletElement)
  1712.             return getEmptyElementTag(node);
  1713.         else if (unwrapObject(node).firebugIgnore)
  1714.             return null;
  1715.         else if (HTMLLib.isContainerElement(node))
  1716.             return expandAll ? Firebug.HTMLPanel.CompleteElement.tag : Firebug.HTMLPanel.Element.tag;
  1717.         else if (HTMLLib.isEmptyElement(node))
  1718.             return getEmptyElementTag(node);
  1719.         else if (Firebug.showCommentNodes && HTMLLib.hasCommentChildren(node))
  1720.             return expandAll ? Firebug.HTMLPanel.CompleteElement.tag : Firebug.HTMLPanel.Element.tag;
  1721.         else if (HTMLLib.hasNoElementChildren(node))
  1722.             return Firebug.HTMLPanel.TextElement.tag;
  1723.         else
  1724.             return expandAll ? Firebug.HTMLPanel.CompleteElement.tag : Firebug.HTMLPanel.Element.tag;
  1725.     }
  1726.     else if (node instanceof Text)
  1727.         return Firebug.HTMLPanel.TextNode.tag;
  1728.     else if (node instanceof CDATASection)
  1729.         return Firebug.HTMLPanel.CDATANode.tag;
  1730.     else if (node instanceof Comment && (Firebug.showCommentNodes || expandAll))
  1731.         return Firebug.HTMLPanel.CommentNode.tag;
  1732.     else if (node instanceof SourceText)
  1733.         return FirebugReps.SourceText.tag;
  1734.     else
  1735.         return FirebugReps.Nada.tag;
  1736. }
  1737.  
  1738. function getNodeBoxTag(nodeBox)
  1739. {
  1740.     var re = /([^\s]+)NodeBox/;
  1741.     var m = re.exec(nodeBox.className);
  1742.     if (!m)
  1743.         return null;
  1744.  
  1745.     var nodeBoxType = m[1];
  1746.     if (nodeBoxType == "container")
  1747.         return Firebug.HTMLPanel.Element.tag;
  1748.     else if (nodeBoxType == "text")
  1749.         return Firebug.HTMLPanel.TextElement.tag;
  1750.     else if (nodeBoxType == "empty")
  1751.         return Firebug.HTMLPanel.EmptyElement.tag;
  1752. }
  1753.  
  1754. // ************************************************************************************************
  1755. // Mutation Breakpoints
  1756.  
  1757. /**
  1758.  * @class Represents {@link Firebug.Debugger} listener. This listener is reponsible for
  1759.  * providing a list of mutation-breakpoints into the Breakpoints side-panel.
  1760.  */
  1761. Firebug.HTMLModule.DebuggerListener =
  1762. {
  1763.     getBreakpoints: function(context, groups)
  1764.     {
  1765.         if (!context.mutationBreakpoints.isEmpty())
  1766.             groups.push(context.mutationBreakpoints);
  1767.     }
  1768. };
  1769.  
  1770. Firebug.HTMLModule.MutationBreakpoints =
  1771. {
  1772.     breakOnNext: function(context, breaking)
  1773.     {
  1774.         context.breakOnNextMutate = breaking;
  1775.     },
  1776.  
  1777.     breakOnNextMutate: function(event, context, type)
  1778.     {
  1779.         if (!context.breakOnNextMutate)
  1780.             return false;
  1781.  
  1782.         // Ignore changes in trees marked with firebugIgnore.
  1783.         if (isAncestorIgnored(event.target))
  1784.             return false;
  1785.  
  1786.         context.breakOnNextMutate = false;
  1787.  
  1788.         this.breakWithCause(event, context, type);
  1789.     },
  1790.  
  1791.     breakWithCause: function(event, context, type)
  1792.     {
  1793.         var changeLabel = Firebug.HTMLModule.BreakpointRep.getChangeLabel({type: type});
  1794.         context.breakingCause = {
  1795.             title: $STR("net.Break On Mutate"),
  1796.             message: changeLabel,
  1797.             type: event.type,
  1798.             target: event.target,
  1799.             relatedNode: event.relatedNode, // http://www.w3.org/TR/DOM-Level-2-Events/events.html
  1800.             prevValue: event.prevValue,
  1801.             newValue: event.newValue,
  1802.             attrName: event.attrName,
  1803.             attrChange: event.attrChange,
  1804.         };
  1805.  
  1806.         Firebug.Breakpoint.breakNow(context.getPanel("html", true));
  1807.         return true;
  1808.     },
  1809.  
  1810.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1811.     // Mutation event handlers.
  1812.  
  1813.     onMutateAttr: function(event, context)
  1814.     {
  1815.         if (this.breakOnNextMutate(event, context, BP_BREAKONATTRCHANGE))
  1816.             return;
  1817.  
  1818.         var breakpoints = context.mutationBreakpoints;
  1819.         var self = this;
  1820.         breakpoints.enumerateBreakpoints(function(bp) {
  1821.             if (bp.checked && bp.node == event.target && bp.type == BP_BREAKONATTRCHANGE) {
  1822.                 self.breakWithCause(event, context, BP_BREAKONATTRCHANGE);
  1823.                 return true;
  1824.             }
  1825.         });
  1826.     },
  1827.  
  1828.     onMutateText: function(event, context)
  1829.     {
  1830.         if (this.breakOnNextMutate(event, context, BP_BREAKONTEXT))
  1831.             return;
  1832.     },
  1833.  
  1834.     onMutateNode: function(event, context)
  1835.     {
  1836.         var node = event.target;
  1837.         var removal = event.type == "DOMNodeRemoved";
  1838.  
  1839.         if (this.breakOnNextMutate(event, context, removal ? BP_BREAKONREMOVE : BP_BREAKONCHILDCHANGE))
  1840.             return;
  1841.  
  1842.         var breakpoints = context.mutationBreakpoints;
  1843.         var breaked = false;
  1844.  
  1845.         if (removal)
  1846.         {
  1847.             var self = this;
  1848.             breaked = breakpoints.enumerateBreakpoints(function(bp) {
  1849.                 if (bp.checked && bp.node == node && bp.type == BP_BREAKONREMOVE) {
  1850.                     self.breakWithCause(event, context, BP_BREAKONREMOVE);
  1851.                     return true;
  1852.                 }
  1853.             });
  1854.         }
  1855.  
  1856.         if (!breaked)
  1857.         {
  1858.             // Collect all parents of the mutated node.
  1859.             var parents = [];
  1860.             for (var parent = node.parentNode; parent; parent = parent.parentNode)
  1861.                 parents.push(parent);
  1862.  
  1863.             // Iterate over all parents and see if some of them has a breakpoint.
  1864.             var self = this;
  1865.             breakpoints.enumerateBreakpoints(function(bp) {
  1866.                 for (var i=0; i<parents.length; i++) {
  1867.                     if (bp.checked && bp.node == parents[i] && bp.type == BP_BREAKONCHILDCHANGE) {
  1868.                         self.breakWithCause(event, context, BP_BREAKONCHILDCHANGE);
  1869.                         return true;
  1870.                     }
  1871.                 }
  1872.             });
  1873.         }
  1874.  
  1875.         if (removal)
  1876.         {
  1877.             // Remove all breakpoints assocaited with removed node.
  1878.             var invalidate = false;
  1879.             breakpoints.enumerateBreakpoints(function(bp) {
  1880.                 if (bp.node == node) {
  1881.                     breakpoints.removeBreakpoint(bp);
  1882.                     invalidate = true;
  1883.                 }
  1884.             });
  1885.  
  1886.             if (invalidate)
  1887.                 context.invalidatePanels("breakpoints");
  1888.         }
  1889.     },
  1890.  
  1891.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1892.     // Context menu items
  1893.  
  1894.     getContextMenuItems: function(context, node, target, items)
  1895.     {
  1896.         if (!(node && node.nodeType == 1))
  1897.             return;
  1898.  
  1899.         var breakpoints = context.mutationBreakpoints;
  1900.  
  1901.         var attrBox = getAncestorByClass(target, "nodeAttr");
  1902.         if (getAncestorByClass(target, "nodeAttr"))
  1903.         {
  1904.         }
  1905.  
  1906.         if (!(nonEditableTags.hasOwnProperty(node.localName)))
  1907.         {
  1908.             items.push(
  1909.                 "-",
  1910.                 {label: "html.label.Break On Attribute Change",
  1911.                     type: "checkbox",
  1912.                     checked: breakpoints.findBreakpoint(node, BP_BREAKONATTRCHANGE),
  1913.                     command: bindFixed(this.onModifyBreakpoint, this, context, node,
  1914.                         BP_BREAKONATTRCHANGE)},
  1915.                 {label: "html.label.Break On Child Addition or Removal",
  1916.                     type: "checkbox",
  1917.                     checked: breakpoints.findBreakpoint(node, BP_BREAKONCHILDCHANGE),
  1918.                     command: bindFixed(this.onModifyBreakpoint, this, context, node,
  1919.                         BP_BREAKONCHILDCHANGE)},
  1920.                 {label: "html.label.Break On Element Removal",
  1921.                     type: "checkbox",
  1922.                     checked: breakpoints.findBreakpoint(node, BP_BREAKONREMOVE),
  1923.                     command: bindFixed(this.onModifyBreakpoint, this, context, node,
  1924.                         BP_BREAKONREMOVE)}
  1925.             );
  1926.         }
  1927.     },
  1928.  
  1929.     onModifyBreakpoint: function(context, node, type)
  1930.     {
  1931.         var breakpoints = context.mutationBreakpoints;
  1932.         var bp = breakpoints.findBreakpoint(node, type);
  1933.  
  1934.         // Remove an existing or create new breakpoint.
  1935.         if (bp)
  1936.             breakpoints.removeBreakpoint(bp);
  1937.         else
  1938.             context.mutationBreakpoints.addBreakpoint(node, type);
  1939.     },
  1940. };
  1941.  
  1942. Firebug.HTMLModule.Breakpoint = function(node, type)
  1943. {
  1944.     this.node = node;
  1945.     this.xpath = getElementXPath(node);
  1946.     this.checked = true;
  1947.     this.type = type;
  1948. }
  1949.  
  1950. Firebug.HTMLModule.BreakpointRep = domplate(Firebug.Rep,
  1951. {
  1952.     inspectable: false,
  1953.  
  1954.     tag:
  1955.         DIV({"class": "breakpointRow focusRow", _repObject: "$bp",
  1956.             role: "option", "aria-checked": "$bp.checked"},
  1957.             DIV({"class": "breakpointBlockHead", onclick: "$onEnable"},
  1958.                 INPUT({"class": "breakpointCheckbox", type: "checkbox",
  1959.                     _checked: "$bp.checked", tabindex : "-1"}),
  1960.                 TAG("$bp.node|getNodeTag", {object: "$bp.node"}),
  1961.                 DIV({"class": "breakpointMutationType"}, "$bp|getChangeLabel"),
  1962.                 IMG({"class": "closeButton", src: "blank.gif", onclick: "$onRemove"})
  1963.             ),
  1964.             DIV({"class": "breakpointCode"},
  1965.                 TAG("$bp.node|getSourceLine", {object: "$bp.node"})
  1966.             )
  1967.         ),
  1968.  
  1969.     getNodeTag: function(node)
  1970.     {
  1971.         var rep = Firebug.getRep(node);
  1972.         return rep.shortTag ? rep.shortTag : rep.tag;
  1973.     },
  1974.  
  1975.     getSourceLine: function(node)
  1976.     {
  1977.         return getNodeTag(node, false);
  1978.     },
  1979.  
  1980.     getChangeLabel: function(bp)
  1981.     {
  1982.         switch (bp.type)
  1983.         {
  1984.         case BP_BREAKONATTRCHANGE:
  1985.             return $STR("html.label.Break On Attribute Change");
  1986.         case BP_BREAKONCHILDCHANGE:
  1987.             return $STR("html.label.Break On Child Addition or Removal");
  1988.         case BP_BREAKONREMOVE:
  1989.             return $STR("html.label.Break On Element Removal");
  1990.         case BP_BREAKONTEXT:
  1991.             return $STR("html.label.Break On Text Change");
  1992.         }
  1993.  
  1994.         return "";
  1995.     },
  1996.  
  1997.     onRemove: function(event)
  1998.     {
  1999.         cancelEvent(event);
  2000.  
  2001.         var bpPanel = Firebug.getElementPanel(event.target);
  2002.         var context = bpPanel.context;
  2003.         var htmlPanel = context.getPanel("html");
  2004.  
  2005.         if (hasClass(event.target, "closeButton"))
  2006.         {
  2007.             // Remove from list of breakpoints.
  2008.             var row = getAncestorByClass(event.target, "breakpointRow");
  2009.             context.mutationBreakpoints.removeBreakpoint(row.repObject);
  2010.  
  2011.             // Remove from the UI.
  2012.             bpPanel.noRefresh = true;
  2013.             bpPanel.removeRow(row);
  2014.             bpPanel.noRefresh = false;
  2015.         }
  2016.     },
  2017.  
  2018.     onEnable: function(event)
  2019.     {
  2020.         var checkBox = event.target;
  2021.         if (hasClass(checkBox, "breakpointCheckbox"))
  2022.         {
  2023.             var bp = getAncestorByClass(checkBox, "breakpointRow").repObject;
  2024.             bp.checked = checkBox.checked;
  2025.         }
  2026.     },
  2027.  
  2028.     supportsObject: function(object)
  2029.     {
  2030.         return object instanceof Firebug.HTMLModule.Breakpoint;
  2031.     }
  2032. });
  2033.  
  2034. // ************************************************************************************************
  2035.  
  2036. function MutationBreakpointGroup()
  2037. {
  2038.     this.breakpoints = [];
  2039. }
  2040.  
  2041. MutationBreakpointGroup.prototype = extend(new Firebug.Breakpoint.BreakpointGroup(),
  2042. {
  2043.     name: "mutationBreakpoints",
  2044.     title: $STR("html.label.HTML Breakpoints"),
  2045.  
  2046.     addBreakpoint: function(node, type)
  2047.     {
  2048.         this.breakpoints.push(new Firebug.HTMLModule.Breakpoint(node, type));
  2049.     },
  2050.  
  2051.     matchBreakpoint: function(bp, args)
  2052.     {
  2053.         var node = args[0];
  2054.         var type = args[1];
  2055.         return (bp.node == node) && (!bp.type || bp.type == type);
  2056.     },
  2057.  
  2058.     removeBreakpoint: function(bp)
  2059.     {
  2060.         remove(this.breakpoints, bp);
  2061.     },
  2062.  
  2063.     // Persistence
  2064.     load: function(context)
  2065.     {
  2066.         var panelState = getPersistedState(context, "html");
  2067.         if (panelState.breakpoints)
  2068.             this.breakpoints = panelState.breakpoints;
  2069.  
  2070.         this.enumerateBreakpoints(function(bp)
  2071.         {
  2072.             var elts = getElementsByXPath(context.window.document, bp.xpath);
  2073.             bp.node = elts && elts.length ? elts[0] : null;
  2074.         });
  2075.     },
  2076.  
  2077.     store: function(context)
  2078.     {
  2079.         this.enumerateBreakpoints(function(bp)
  2080.         {
  2081.             bp.node = null;
  2082.         });
  2083.  
  2084.         var panelState = getPersistedState(context, "html");
  2085.         panelState.breakpoints = this.breakpoints;
  2086.     },
  2087. });
  2088.  
  2089.  
  2090.  
  2091.  
  2092. // ************************************************************************************************
  2093. // Registration
  2094.  
  2095. Firebug.registerPanel(Firebug.HTMLPanel);
  2096. Firebug.registerModule(Firebug.HTMLModule);
  2097. Firebug.registerRep(Firebug.HTMLModule.BreakpointRep);
  2098.  
  2099. // ************************************************************************************************
  2100. }});
  2101.